/*
 * Copyright 2016 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package io.netty.handler.codec.http2;

import io.netty.handler.codec.Headers;
import io.netty.util.AsciiString;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

import static io.netty.handler.codec.CharSequenceValueConverter.INSTANCE;
import static io.netty.handler.codec.http2.DefaultHttp2Headers.HTTP2_NAME_VALIDATOR;
import static io.netty.util.internal.EmptyArrays.EMPTY_ASCII_STRINGS;

/**
 * A variant of {@link Http2Headers} which only supports read-only methods.
 * <p>
 * Any array passed to this class may be used directly in the underlying data
 * structures of this class. If these arrays may be modified it is the caller's
 * responsibility to supply this class with a copy of the array.
 * <p>
 * This may be a good alternative to {@link DefaultHttp2Headers} if your have a
 * fixed set of headers which will not change.
 */
public final class ReadOnlyHttp2Headers implements Http2Headers
{
    private static final byte PSEUDO_HEADER_TOKEN = (byte) ':';

    private final AsciiString[] pseudoHeaders;

    private final AsciiString[] otherHeaders;

    /**
     * Used to create read only object designed to represent trailers.
     * <p>
     * If this is used for a purpose other than trailers you may violate the
     * header serialization ordering defined by
     * <a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.1">RFC 7540,
     * 8.1.2.1</a>.
     * @param validateHeaders {@code true} will run validation on each header
     *        name/value pair to ensure protocol compliance.
     * @param otherHeaders A an array of key:value pairs. Must not contain any
     *        <a href=
     *        "https://tools.ietf.org/html/rfc7540#section-8.1.2.1">pseudo
     *        headers</a> or {@code null} names/values. A copy will
     *        <strong>NOT</strong> be made of this array. If the contents of
     *        this array may be modified externally you are responsible for
     *        passing in a copy.
     * @return A read only representation of the headers.
     */
    public static ReadOnlyHttp2Headers trailers(boolean validateHeaders,
            AsciiString... otherHeaders)
    {
        return new ReadOnlyHttp2Headers(validateHeaders, EMPTY_ASCII_STRINGS,
                otherHeaders);
    }

    /**
     * Create a new read only representation of headers used by clients.
     * @param validateHeaders {@code true} will run validation on each header
     *        name/value pair to ensure protocol compliance.
     * @param method The value for {@link PseudoHeaderName#METHOD}.
     * @param path The value for {@link PseudoHeaderName#PATH}.
     * @param scheme The value for {@link PseudoHeaderName#SCHEME}.
     * @param authority The value for {@link PseudoHeaderName#AUTHORITY}.
     * @param otherHeaders A an array of key:value pairs. Must not contain any
     *        <a href=
     *        "https://tools.ietf.org/html/rfc7540#section-8.1.2.1">pseudo
     *        headers</a> or {@code null} names/values. A copy will
     *        <strong>NOT</strong> be made of this array. If the contents of
     *        this array may be modified externally you are responsible for
     *        passing in a copy.
     * @return a new read only representation of headers used by clients.
     */
    public static ReadOnlyHttp2Headers clientHeaders(boolean validateHeaders,
            AsciiString method, AsciiString path, AsciiString scheme,
            AsciiString authority, AsciiString... otherHeaders)
    {
        return new ReadOnlyHttp2Headers(validateHeaders, new AsciiString[]
        { PseudoHeaderName.METHOD.value(), method,
                PseudoHeaderName.PATH.value(), path,
                PseudoHeaderName.SCHEME.value(), scheme,
                PseudoHeaderName.AUTHORITY.value(), authority }, otherHeaders);
    }

    /**
     * Create a new read only representation of headers used by servers.
     * @param validateHeaders {@code true} will run validation on each header
     *        name/value pair to ensure protocol compliance.
     * @param status The value for {@link PseudoHeaderName#STATUS}.
     * @param otherHeaders A an array of key:value pairs. Must not contain any
     *        <a href=
     *        "https://tools.ietf.org/html/rfc7540#section-8.1.2.1">pseudo
     *        headers</a> or {@code null} names/values. A copy will
     *        <strong>NOT</strong> be made of this array. If the contents of
     *        this array may be modified externally you are responsible for
     *        passing in a copy.
     * @return a new read only representation of headers used by servers.
     */
    public static ReadOnlyHttp2Headers serverHeaders(boolean validateHeaders,
            AsciiString status, AsciiString... otherHeaders)
    {
        return new ReadOnlyHttp2Headers(validateHeaders, new AsciiString[]
        { PseudoHeaderName.STATUS.value(), status }, otherHeaders);
    }

    private ReadOnlyHttp2Headers(boolean validateHeaders,
            AsciiString[] pseudoHeaders, AsciiString... otherHeaders)
    {
        assert (pseudoHeaders.length & 1) == 0; // pseudoHeaders are only set
                                                // internally so assert should
                                                // be enough.
        if ((otherHeaders.length & 1) != 0)
        {
            throw newInvalidArraySizeException();
        }
        if (validateHeaders)
        {
            validateHeaders(pseudoHeaders, otherHeaders);
        }
        this.pseudoHeaders = pseudoHeaders;
        this.otherHeaders = otherHeaders;
    }

    private static IllegalArgumentException newInvalidArraySizeException()
    {
        return new IllegalArgumentException(
                "pseudoHeaders and otherHeaders must be arrays of [name, value] pairs");
    }

    private static void validateHeaders(AsciiString[] pseudoHeaders,
            AsciiString... otherHeaders)
    {
        // We are only validating values... so start at 1 and go until end.
        for (int i = 1; i < pseudoHeaders.length; i += 2)
        {
            // pseudoHeaders names are only set internally so they are assumed
            // to be valid.
            if (pseudoHeaders[i] == null)
            {
                throw new IllegalArgumentException(
                        "pseudoHeaders value at index " + i + " is null");
            }
        }

        boolean seenNonPseudoHeader = false;
        final int otherHeadersEnd = otherHeaders.length - 1;
        for (int i = 0; i < otherHeadersEnd; i += 2)
        {
            AsciiString name = otherHeaders[i];
            HTTP2_NAME_VALIDATOR.validateName(name);
            if (!seenNonPseudoHeader && !name.isEmpty()
                    && name.byteAt(0) != PSEUDO_HEADER_TOKEN)
            {
                seenNonPseudoHeader = true;
            }
            else if (seenNonPseudoHeader && !name.isEmpty()
                    && name.byteAt(0) == PSEUDO_HEADER_TOKEN)
            {
                throw new IllegalArgumentException("otherHeaders name at index "
                        + i
                        + " is a pseudo header that appears after non-pseudo headers.");
            }
            if (otherHeaders[i + 1] == null)
            {
                throw new IllegalArgumentException(
                        "otherHeaders value at index " + (i + 1) + " is null");
            }
        }
    }

    private AsciiString get0(CharSequence name)
    {
        final int nameHash = AsciiString.hashCode(name);

        final int pseudoHeadersEnd = pseudoHeaders.length - 1;
        for (int i = 0; i < pseudoHeadersEnd; i += 2)
        {
            AsciiString roName = pseudoHeaders[i];
            if (roName.hashCode() == nameHash
                    && roName.contentEqualsIgnoreCase(name))
            {
                return pseudoHeaders[i + 1];
            }
        }

        final int otherHeadersEnd = otherHeaders.length - 1;
        for (int i = 0; i < otherHeadersEnd; i += 2)
        {
            AsciiString roName = otherHeaders[i];
            if (roName.hashCode() == nameHash
                    && roName.contentEqualsIgnoreCase(name))
            {
                return otherHeaders[i + 1];
            }
        }
        return null;
    }

    @Override
    public CharSequence get(CharSequence name)
    {
        return get0(name);
    }

    @Override
    public CharSequence get(CharSequence name, CharSequence defaultValue)
    {
        CharSequence value = get(name);
        return value != null ? value : defaultValue;
    }

    @Override
    public CharSequence getAndRemove(CharSequence name)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public CharSequence getAndRemove(CharSequence name,
            CharSequence defaultValue)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public List<CharSequence> getAll(CharSequence name)
    {
        final int nameHash = AsciiString.hashCode(name);
        List<CharSequence> values = new ArrayList<CharSequence>();

        final int pseudoHeadersEnd = pseudoHeaders.length - 1;
        for (int i = 0; i < pseudoHeadersEnd; i += 2)
        {
            AsciiString roName = pseudoHeaders[i];
            if (roName.hashCode() == nameHash
                    && roName.contentEqualsIgnoreCase(name))
            {
                values.add(pseudoHeaders[i + 1]);
            }
        }

        final int otherHeadersEnd = otherHeaders.length - 1;
        for (int i = 0; i < otherHeadersEnd; i += 2)
        {
            AsciiString roName = otherHeaders[i];
            if (roName.hashCode() == nameHash
                    && roName.contentEqualsIgnoreCase(name))
            {
                values.add(otherHeaders[i + 1]);
            }
        }

        return values;
    }

    @Override
    public List<CharSequence> getAllAndRemove(CharSequence name)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Boolean getBoolean(CharSequence name)
    {
        AsciiString value = get0(name);
        return value != null ? INSTANCE.convertToBoolean(value) : null;
    }

    @Override
    public boolean getBoolean(CharSequence name, boolean defaultValue)
    {
        Boolean value = getBoolean(name);
        return value != null ? value : defaultValue;
    }

    @Override
    public Byte getByte(CharSequence name)
    {
        AsciiString value = get0(name);
        return value != null ? INSTANCE.convertToByte(value) : null;
    }

    @Override
    public byte getByte(CharSequence name, byte defaultValue)
    {
        Byte value = getByte(name);
        return value != null ? value : defaultValue;
    }

    @Override
    public Character getChar(CharSequence name)
    {
        AsciiString value = get0(name);
        return value != null ? INSTANCE.convertToChar(value) : null;
    }

    @Override
    public char getChar(CharSequence name, char defaultValue)
    {
        Character value = getChar(name);
        return value != null ? value : defaultValue;
    }

    @Override
    public Short getShort(CharSequence name)
    {
        AsciiString value = get0(name);
        return value != null ? INSTANCE.convertToShort(value) : null;
    }

    @Override
    public short getShort(CharSequence name, short defaultValue)
    {
        Short value = getShort(name);
        return value != null ? value : defaultValue;
    }

    @Override
    public Integer getInt(CharSequence name)
    {
        AsciiString value = get0(name);
        return value != null ? INSTANCE.convertToInt(value) : null;
    }

    @Override
    public int getInt(CharSequence name, int defaultValue)
    {
        Integer value = getInt(name);
        return value != null ? value : defaultValue;
    }

    @Override
    public Long getLong(CharSequence name)
    {
        AsciiString value = get0(name);
        return value != null ? INSTANCE.convertToLong(value) : null;
    }

    @Override
    public long getLong(CharSequence name, long defaultValue)
    {
        Long value = getLong(name);
        return value != null ? value : defaultValue;
    }

    @Override
    public Float getFloat(CharSequence name)
    {
        AsciiString value = get0(name);
        return value != null ? INSTANCE.convertToFloat(value) : null;
    }

    @Override
    public float getFloat(CharSequence name, float defaultValue)
    {
        Float value = getFloat(name);
        return value != null ? value : defaultValue;
    }

    @Override
    public Double getDouble(CharSequence name)
    {
        AsciiString value = get0(name);
        return value != null ? INSTANCE.convertToDouble(value) : null;
    }

    @Override
    public double getDouble(CharSequence name, double defaultValue)
    {
        Double value = getDouble(name);
        return value != null ? value : defaultValue;
    }

    @Override
    public Long getTimeMillis(CharSequence name)
    {
        AsciiString value = get0(name);
        return value != null ? INSTANCE.convertToTimeMillis(value) : null;
    }

    @Override
    public long getTimeMillis(CharSequence name, long defaultValue)
    {
        Long value = getTimeMillis(name);
        return value != null ? value : defaultValue;
    }

    @Override
    public Boolean getBooleanAndRemove(CharSequence name)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public boolean getBooleanAndRemove(CharSequence name, boolean defaultValue)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Byte getByteAndRemove(CharSequence name)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public byte getByteAndRemove(CharSequence name, byte defaultValue)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Character getCharAndRemove(CharSequence name)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public char getCharAndRemove(CharSequence name, char defaultValue)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Short getShortAndRemove(CharSequence name)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public short getShortAndRemove(CharSequence name, short defaultValue)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Integer getIntAndRemove(CharSequence name)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public int getIntAndRemove(CharSequence name, int defaultValue)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Long getLongAndRemove(CharSequence name)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public long getLongAndRemove(CharSequence name, long defaultValue)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Float getFloatAndRemove(CharSequence name)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public float getFloatAndRemove(CharSequence name, float defaultValue)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Double getDoubleAndRemove(CharSequence name)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public double getDoubleAndRemove(CharSequence name, double defaultValue)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Long getTimeMillisAndRemove(CharSequence name)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public long getTimeMillisAndRemove(CharSequence name, long defaultValue)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public boolean contains(CharSequence name)
    {
        return get(name) != null;
    }

    @Override
    public boolean contains(CharSequence name, CharSequence value)
    {
        final int nameHash = AsciiString.hashCode(name);
        final int valueHash = AsciiString.hashCode(value);

        final int pseudoHeadersEnd = pseudoHeaders.length - 1;
        for (int i = 0; i < pseudoHeadersEnd; i += 2)
        {
            AsciiString roName = pseudoHeaders[i];
            AsciiString roValue = pseudoHeaders[i + 1];
            if (roName.hashCode() == nameHash && roValue.hashCode() == valueHash
                    && roName.contentEqualsIgnoreCase(name)
                    && roValue.contentEqualsIgnoreCase(value))
            {
                return true;
            }
        }

        final int otherHeadersEnd = otherHeaders.length - 1;
        for (int i = 0; i < otherHeadersEnd; i += 2)
        {
            AsciiString roName = otherHeaders[i];
            AsciiString roValue = otherHeaders[i + 1];
            if (roName.hashCode() == nameHash && roValue.hashCode() == valueHash
                    && roName.contentEqualsIgnoreCase(name)
                    && roValue.contentEqualsIgnoreCase(value))
            {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean containsObject(CharSequence name, Object value)
    {
        if (value instanceof CharSequence)
        {
            return contains(name, (CharSequence) value);
        }
        return contains(name, value.toString());
    }

    @Override
    public boolean containsBoolean(CharSequence name, boolean value)
    {
        return contains(name, String.valueOf(value));
    }

    @Override
    public boolean containsByte(CharSequence name, byte value)
    {
        return contains(name, String.valueOf(value));
    }

    @Override
    public boolean containsChar(CharSequence name, char value)
    {
        return contains(name, String.valueOf(value));
    }

    @Override
    public boolean containsShort(CharSequence name, short value)
    {
        return contains(name, String.valueOf(value));
    }

    @Override
    public boolean containsInt(CharSequence name, int value)
    {
        return contains(name, String.valueOf(value));
    }

    @Override
    public boolean containsLong(CharSequence name, long value)
    {
        return contains(name, String.valueOf(value));
    }

    @Override
    public boolean containsFloat(CharSequence name, float value)
    {
        return false;
    }

    @Override
    public boolean containsDouble(CharSequence name, double value)
    {
        return contains(name, String.valueOf(value));
    }

    @Override
    public boolean containsTimeMillis(CharSequence name, long value)
    {
        return contains(name, String.valueOf(value));
    }

    @Override
    public int size()
    {
        return (pseudoHeaders.length + otherHeaders.length) >>> 1;
    }

    @Override
    public boolean isEmpty()
    {
        return pseudoHeaders.length == 0 && otherHeaders.length == 0;
    }

    @Override
    public Set<CharSequence> names()
    {
        if (isEmpty())
        {
            return Collections.emptySet();
        }
        Set<CharSequence> names = new LinkedHashSet<CharSequence>(size());
        final int pseudoHeadersEnd = pseudoHeaders.length - 1;
        for (int i = 0; i < pseudoHeadersEnd; i += 2)
        {
            names.add(pseudoHeaders[i]);
        }

        final int otherHeadersEnd = otherHeaders.length - 1;
        for (int i = 0; i < otherHeadersEnd; i += 2)
        {
            names.add(otherHeaders[i]);
        }
        return names;
    }

    @Override
    public Http2Headers add(CharSequence name, CharSequence value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers add(CharSequence name,
            Iterable<? extends CharSequence> values)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers add(CharSequence name, CharSequence... values)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers addObject(CharSequence name, Object value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers addObject(CharSequence name, Iterable<?> values)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers addObject(CharSequence name, Object... values)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers addBoolean(CharSequence name, boolean value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers addByte(CharSequence name, byte value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers addChar(CharSequence name, char value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers addShort(CharSequence name, short value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers addInt(CharSequence name, int value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers addLong(CharSequence name, long value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers addFloat(CharSequence name, float value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers addDouble(CharSequence name, double value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers addTimeMillis(CharSequence name, long value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers add(
            Headers<? extends CharSequence, ? extends CharSequence, ?> headers)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers set(CharSequence name, CharSequence value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers set(CharSequence name,
            Iterable<? extends CharSequence> values)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers set(CharSequence name, CharSequence... values)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers setObject(CharSequence name, Object value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers setObject(CharSequence name, Iterable<?> values)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers setObject(CharSequence name, Object... values)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers setBoolean(CharSequence name, boolean value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers setByte(CharSequence name, byte value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers setChar(CharSequence name, char value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers setShort(CharSequence name, short value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers setInt(CharSequence name, int value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers setLong(CharSequence name, long value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers setFloat(CharSequence name, float value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers setDouble(CharSequence name, double value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers setTimeMillis(CharSequence name, long value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers set(
            Headers<? extends CharSequence, ? extends CharSequence, ?> headers)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers setAll(
            Headers<? extends CharSequence, ? extends CharSequence, ?> headers)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public boolean remove(CharSequence name)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers clear()
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Iterator<Map.Entry<CharSequence, CharSequence>> iterator()
    {
        return new ReadOnlyIterator();
    }

    @Override
    public Http2Headers method(CharSequence value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers scheme(CharSequence value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers authority(CharSequence value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers path(CharSequence value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public Http2Headers status(CharSequence value)
    {
        throw new UnsupportedOperationException("read only");
    }

    @Override
    public CharSequence method()
    {
        return get(PseudoHeaderName.METHOD.value());
    }

    @Override
    public CharSequence scheme()
    {
        return get(PseudoHeaderName.SCHEME.value());
    }

    @Override
    public CharSequence authority()
    {
        return get(PseudoHeaderName.AUTHORITY.value());
    }

    @Override
    public CharSequence path()
    {
        return get(PseudoHeaderName.PATH.value());
    }

    @Override
    public CharSequence status()
    {
        return get(PseudoHeaderName.STATUS.value());
    }

    @Override
    public String toString()
    {
        StringBuilder builder = new StringBuilder(getClass().getSimpleName())
                .append('[');
        String separator = "";
        for (Map.Entry<CharSequence, CharSequence> entry : this)
        {
            builder.append(separator);
            builder.append(entry.getKey()).append(": ")
                    .append(entry.getValue());
            separator = ", ";
        }
        return builder.append(']').toString();
    }

    private final class ReadOnlyIterator
            implements Map.Entry<CharSequence, CharSequence>,
            Iterator<Map.Entry<CharSequence, CharSequence>>
    {
        private int i;

        private AsciiString[] current = pseudoHeaders.length != 0
                ? pseudoHeaders : otherHeaders;

        private AsciiString key;

        private AsciiString value;

        @Override
        public boolean hasNext()
        {
            return i != current.length;
        }

        @Override
        public Map.Entry<CharSequence, CharSequence> next()
        {
            if (!hasNext())
            {
                throw new NoSuchElementException();
            }
            key = current[i];
            value = current[i + 1];
            i += 2;
            if (i == current.length && current == pseudoHeaders)
            {
                current = otherHeaders;
                i = 0;
            }
            return this;
        }

        @Override
        public CharSequence getKey()
        {
            return key;
        }

        @Override
        public CharSequence getValue()
        {
            return value;
        }

        @Override
        public CharSequence setValue(CharSequence value)
        {
            throw new UnsupportedOperationException("read only");
        }

        @Override
        public void remove()
        {
            throw new UnsupportedOperationException("read only");
        }

        @Override
        public String toString()
        {
            return key.toString() + '=' + value.toString();
        }
    }
}
